#pragma semicolon 1 // Force strict semicolon mode.

#include <sourcemod>
#include <sdktools>
#include <tf2_stocks>
#include <tfx>
#include <psycore>

#define PLUGIN_NAME              "psyFX - PowerUps"
#define PLUGIN_AUTHOR            "Thrawn"
#define PLUGIN_VERSION           "1.0.0.0"
#define PLUGIN_CONTACT           "http://aaa.einfachonline.net/"

#define PLAYER_SLOW     (1 << 0)

#define SOUND_LUCKY 			"vo/SandwichEat09.wav"
#define SOUND_HOTAIR 			"vo/scout_apexofjump01.wav"
#define SOUND_SENTRY 			"vo/engineer_specialcompleted06.wav"
#define SOUND_RUNNINGSHOES 		"vo/scout_invincible03.wav"
#define SOUND_RUNNINGSHOES_END 	"player/pl_scout_dodge_tired.wav"
#define SOUND_FLUBBER			"player/pl_scout_jump1.wav"
#define SOUND_DRACULA			"vo/medic_laughevil05.wav"
#define SOUND_POWERPLAY			"vo/soldier_directhittaunt02.wav"
#define SOUND_INSTACHARGE		"vo/demoman_go01.wav"
#define SOUND_BUFFWHISTLE		"weapons/buffed_on.wav"
#define SOUND_BUFFWHISTLE_END	"weapons/buffed_off.wav"


new g_iEffectNum[8] = {-1,...};
new Handle:g_clientEffectTimer[MAXPLAYERS + 1][2];
new bool:g_bHasLuckySandvich[MAXPLAYERS + 1] = {false, ...};
new bool:g_bHasHotAir[MAXPLAYERS + 1] = {false, ...};
new bool:g_bHasShoes[MAXPLAYERS + 1] = {false, ...};
new bool:g_bHasDracula[MAXPLAYERS + 1] = {false, ...};
new bool:g_bHasPowerPlay[MAXPLAYERS + 1] = {false, ...};

new bool:g_bCanDoPowerplay = true;
new Handle:g_hPowerplay = INVALID_HANDLE;


public Plugin:myinfo = {
    name        = PLUGIN_NAME,
    author      = PLUGIN_AUTHOR,
    description = PLUGIN_NAME,
    version     = PLUGIN_VERSION,
    url         = PLUGIN_CONTACT
};

public OnPluginStart() {
	new Handle:hPPGamedata = LoadGameConfigFile("powerplay.tf");
	if (hPPGamedata == INVALID_HANDLE)
	{
		LogError("Unable to find powerplay.tf.txt");
		g_bCanDoPowerplay = false;
	}
	StartPrepSDKCall(SDKCall_Player);
	if (!PrepSDKCall_SetFromConf(hPPGamedata, SDKConf_Signature, "SetPowerplayEnabled"))
	{
		LogError("Unable to find SetPowerplayEnabled in powerplay.tf.txt");
		g_bCanDoPowerplay = false;
	}
	if (hPPGamedata != INVALID_HANDLE)
	{
		CloseHandle(hPPGamedata);
	}
	PrepSDKCall_AddParameter(SDKType_Bool, SDKPass_Plain);
	g_hPowerplay = EndPrepSDKCall();
	if (g_hPowerplay == INVALID_HANDLE)
	{
		LogError("Could not find CTFPlayer::SetPowerplayEnabled. Disabling to prevent crashes");
		g_bCanDoPowerplay = false;
	}
}

public OnMapStart()
{
	//PrecacheModel(MODEL_CONE, true);
	PrecacheSound(SOUND_LUCKY, true);	
	PrecacheSound(SOUND_HOTAIR, true);
	PrecacheSound(SOUND_SENTRY, true);
	PrecacheSound(SOUND_RUNNINGSHOES, true);
	PrecacheSound(SOUND_RUNNINGSHOES_END, true);
	PrecacheSound(SOUND_FLUBBER, true);
	PrecacheSound(SOUND_DRACULA, true);
	PrecacheSound(SOUND_POWERPLAY, true);
	PrecacheSound(SOUND_INSTACHARGE, true);	
	PrecacheSound(SOUND_BUFFWHISTLE, true);	
	PrecacheSound(SOUND_BUFFWHISTLE_END, true);		
}

public OnAllPluginsLoaded() {
	if(LibraryExists("psycore")) {
		g_iEffectNum[0] = psyRTD_RegisterEffect(psyRTDEffectType_Good,"Lucky Sandwich","You've got one free critical - Use it well", EffectLuckySandwich);
		g_iEffectNum[1] = psyRTD_RegisterEffect(psyRTDEffectType_Good,"Hot Air Balloon","You feel as light as a feather!", EffectHotAir);
		g_iEffectNum[2] = psyRTD_RegisterEffect(psyRTDEffectType_Good,"Instant Sentry","Just add water", EffectSentry);
		g_iEffectNum[3] = psyRTD_RegisterEffect(psyRTDEffectType_Good,"Running Shoes","You feel a spring in your step", EffectRunningShoes);
		g_iEffectNum[4] = psyRTD_RegisterEffect(psyRTDEffectType_Good,"Dracula's Heart","Leech the life of your foes", EffectDracula);		
		g_iEffectNum[5] = psyRTD_RegisterEffect(psyRTDEffectType_Good,"Buff Whistle","Dubi-dubi-duu!", EffectBuffWhistle);
		
		if(g_bCanDoPowerplay) {
			g_iEffectNum[6] = psyRTD_RegisterEffect(psyRTDEffectType_Good,"Power Play","Do the Robin!", EffectPowerPlay);
		}
		
		//g_iEffectNum[7] = psyRTD_RegisterEffect(psyRTDEffectType_Good,"Insta Charge","Raaaaageeee!",EffectInstaCharge);
	}		
}

public psyRTD_DisableEffect(client,effectId) {
	for(new i = 0; i < sizeof(g_iEffectNum); i++) {
		if(effectId == g_iEffectNum[i]) {
			if(g_clientEffectTimer[client][0] != INVALID_HANDLE)
				TriggerTimer(g_clientEffectTimer[client][0]);

			if(g_clientEffectTimer[client][1] != INVALID_HANDLE)
				TriggerTimer(g_clientEffectTimer[client][1]);
		}
	}
}

public Action:TF2_CalcIsAttackCritical(client, weapon, String:weaponname[], &bool:result)
{
	//if (Loot_IsEnabled()) {
		/* That's one lucky sandwich */
		if (g_bHasLuckySandvich[client]) {
			result = true;
			
			g_bHasLuckySandvich[client] = false;
			psyRTD_EffectIsNowDisabled(client, g_iEffectNum[0]);
			
			return Plugin_Handled;
		} else {
			return Plugin_Continue;
		}
	//} else {
	//	return Plugin_Continue;	
	//}
}


public psyRTDAction:EffectDracula(client)
{
	g_bHasDracula[client] = true;
	
	g_clientEffectTimer[client][0] = CreateTimer(15.0, Timer_Dracula, client);
	g_clientEffectTimer[client][1] = CreateTimer(0.1, Timer_DraculaBlood, client, TIMER_REPEAT);
	
	EmitSoundClient(SOUND_DRACULA, client);
	
	return psyRTD_Continue;
}

public Action:Timer_DraculaBlood(Handle:timer, any:client)
{
	if (!g_bHasDracula[client]) {
		return Plugin_Stop;
	} else {
		CreateParticle("blood_impact_red_01_chunk", 0.1, client, ATTACH_HEAD);
		
		return Plugin_Continue;
	}
}

public Action:Timer_Dracula(Handle:timer, any:client)
{
	g_bHasDracula[client] = false;
	g_clientEffectTimer[client][0] = INVALID_HANDLE;
	g_clientEffectTimer[client][1] = INVALID_HANDLE;
	
	if (client && IsClientInGame(client) && !IsClientInKickQueue(client)) {
		psyRTD_EffectIsNowDisabled(client, g_iEffectNum[4]);
	}
}

public psyRTDAction:EffectRunningShoes(client)
{
	g_bHasShoes[client] = true;
	
	g_clientEffectTimer[client][0] = CreateTimer(20.0, Timer_RunningShoes, client);
	g_clientEffectTimer[client][1] = CreateTimer(0.1, Timer_RunningShoesTrail, client, TIMER_REPEAT);
	
	SetEntPropFloat(client, Prop_Send, "m_flMaxspeed", 400.0);
	
	EmitSoundClient(SOUND_RUNNINGSHOES, client);
	
	return psyRTD_Continue;
}

public Action:Timer_RunningShoesTrail(Handle:timer, any:client)
{
	if (!g_bHasShoes[client]) {
		return Plugin_Stop;
	} else {
		// Allows Heavys/Snipers/Grabbing-Intel to not reduce your speed
		SetEntPropFloat(client, Prop_Send, "m_flMaxspeed", 400.0);
		
		CreateParticle("burningplayer_corpse", 0.5, client, NO_ATTACH, GetRandomFloat(0.0, 32.0) - 16.0, GetRandomFloat(0.0, 32.0) - 16.0);
		
		return Plugin_Continue;
	}
}

public Action:Timer_RunningShoes(Handle:timer, any:client)
{
	if (g_clientEffectTimer[client][1] != INVALID_HANDLE) {
		KillTimer(g_clientEffectTimer[client][1]);
		g_clientEffectTimer[client][1] = INVALID_HANDLE;
	}

	g_bHasShoes[client] = false;	
	g_clientEffectTimer[client][0] = INVALID_HANDLE;
	
	if (client && IsClientInGame(client) && !IsClientInKickQueue(client)) {
		new cond = GetEntProp(client, Prop_Send, "m_nPlayerCond");
		
		// Ensures spun-up Heavys/Snipers don't return to their normal movement speed!
		if (cond & (PLAYER_SLOW)) {
			SetEntPropFloat(client, Prop_Send, "m_flMaxspeed", 80.0);
		} else {
			SetEntPropFloat(client, Prop_Send, "m_flMaxspeed", TF2_GetClassSpeed(TF2_GetPlayerClass(client)));
		}
	
		EmitSoundClient(SOUND_RUNNINGSHOES_END, client);
		psyRTD_EffectIsNowDisabled(client, g_iEffectNum[3]);
	}
}

public psyRTDAction:EffectLuckySandwich(client)
{
	g_bHasLuckySandvich[client] = true;
	
	SetEntityHealth(client, GetClientHealth(client) + 100);
	
	CreateParticle("healhuff_blu", 5.0, client, ATTACH_HEAD);
	CreateParticle("healhuff_red", 5.0, client, ATTACH_HEAD);
	
	EmitSoundClient(SOUND_LUCKY, client);
	
	return psyRTD_Continue;
}


public psyRTDAction:EffectHotAir(client)
{
	g_bHasHotAir[client] = true;
	
	g_clientEffectTimer[client][0] = CreateTimer(15.0, Timer_HotAir, client);
	g_clientEffectTimer[client][1] = CreateTimer(2.0, Timer_HotAirBalloons, client, TIMER_REPEAT);
	
	SetEntityGravity(client, 0.15);
	
	CreateParticle("bday_balloon02", 5.0, client);
	
	EmitSoundClient(SOUND_HOTAIR, client);
	
	return psyRTD_Continue;
}

public Action:Timer_HotAirBalloons(Handle:timer, any:client)
{
	if (!g_bHasHotAir[client]) {
		return Plugin_Stop;
	} else {
		CreateParticle("bday_1balloon", 5.0, client, NO_ATTACH, GetRandomFloat(0.0, 32.0) - 16.0, GetRandomFloat(0.0, 32.0) - 16.0);
		
		return Plugin_Continue;
	}
}

public Action:Timer_HotAir(Handle:timer, any:client)
{
	if (g_clientEffectTimer[client][1] != INVALID_HANDLE) {
		KillTimer(g_clientEffectTimer[client][1]);
		g_clientEffectTimer[client][1] = INVALID_HANDLE;
	}
	
	g_bHasHotAir[client] = false;
	g_clientEffectTimer[client][0] = INVALID_HANDLE;
	
	if (client && IsClientInGame(client) && !IsClientInKickQueue(client)) {
		SetEntityGravity(client, 1.0);
		
		psyRTD_EffectIsNowDisabled(client, g_iEffectNum[1]);
	}
}

public psyRTDAction:EffectBuffWhistle(client) {
	g_clientEffectTimer[client][0] = CreateTimer(5.0, Timer_BuffWhistle, client);

	AddCond(client, 16);
	EmitSoundClient(SOUND_BUFFWHISTLE, client);
	
	return psyRTD_Continue;
}


public Action:Timer_BuffWhistle(Handle:timer, any:client) {
	g_clientEffectTimer[client][0] = INVALID_HANDLE;
	
	RemoveCond(client, 16);
	
	if (client && IsClientInGame(client) && !IsClientInKickQueue(client)) {
		psyRTD_EffectIsNowDisabled(client, g_iEffectNum[5]);
		EmitSoundClient(SOUND_BUFFWHISTLE_END, client);		
	}	
}

public psyRTDAction:EffectInstaCharge(client) {
	//FIXME
	g_clientEffectTimer[client][0] = CreateTimer(2.0, Timer_InstaCharge, client);

	new weapon = GetPlayerWeaponSlot(client, 2);
	if (weapon > -1)
	{
		SetEntPropEnt(client, Prop_Send, "m_hActiveWeapon", weapon);
	}	

	AddCond(client, 17);
	EmitSoundClient(SOUND_INSTACHARGE, client);
	
	return psyRTD_Continue;
}

public Action:Timer_InstaCharge(Handle:timer, any:client) {
	g_clientEffectTimer[client][0] = INVALID_HANDLE;
	
	RemoveCond(client, 17);
	
	if (client && IsClientInGame(client) && !IsClientInKickQueue(client)) {
		psyRTD_EffectIsNowDisabled(client, g_iEffectNum[7]);
	}	
}


public psyRTDAction:EffectPowerPlay(client) {
	if(g_bCanDoPowerplay) {
		g_clientEffectTimer[client][0] = CreateTimer(4.0, Timer_PowerPlay, client);
		g_bHasPowerPlay[client] = true;

		SDKCall(g_hPowerplay, client, true);
		EmitSoundClient(SOUND_POWERPLAY, client);
		
		return psyRTD_Continue;
	}
	return psyRTD_Reroll;
}

public Action:Timer_PowerPlay(Handle:timer, any:client) {
	g_clientEffectTimer[client][0] = INVALID_HANDLE;
	
	g_bHasPowerPlay[client] = false;
	SDKCall(g_hPowerplay, client, false);
	
	if (client && IsClientInGame(client) && !IsClientInKickQueue(client)) {
		psyRTD_EffectIsNowDisabled(client, g_iEffectNum[6]);
	}	
}

public psyRTDAction:EffectSentry(client)
{
	new Float:pos[3], Float:ang[3] = {0.0, 0.0, 0.0}, sentry, Handle:pack;
	
	g_clientEffectTimer[client][0] = CreateDataTimer(10.0, Timer_Sentry, pack);
	
	GetClientAbsOrigin(client, pos);
	
	sentry = BuildSentry(client, pos, ang);
	
	WritePackCell(pack, client);
	WritePackCell(pack, sentry);
	
	// Make it noclip so you don't get stuck
	SetEntProp(sentry, Prop_Data, "m_CollisionGroup", 5);
	SetEntData(sentry, FindSendPropOffs("CObjectSentrygun", "m_iAmmoShells"), 8, 4, true);

	EmitSoundClient(SOUND_SENTRY, client);
	
	return psyRTD_Continue;
}

public Action:Timer_Sentry(Handle:timer, Handle:pack)
{
	new client, sentry;
	ResetPack(pack);
	client = ReadPackCell(pack);
	sentry = ReadPackCell(pack);
	
	g_clientEffectTimer[client][0] = INVALID_HANDLE;
	
	if (IsValidEntity(sentry)) {
		DestroyBuilding(sentry);
	}
	
	if (client && IsClientInGame(client) && !IsClientInKickQueue(client)) {
		psyRTD_EffectIsNowDisabled(client, g_iEffectNum[2]);
	}
}

/* BuildSentry()
**
** Builds a sentry.
**
** Credit to The JCS and Muridas.
** ------------------------------------------------------------------------- */
stock BuildSentry(iBuilder, Float:fOrigin[3], Float:fAngle[3], iLevel=1)
{
	new Float:fBuildMaxs[3] = {24.0, 24.0, 66.0};
	new Float:fMdlWidth[3] = {1.0, 0.5, 0.0};

	new String:sModel[64];
	new iTeam = GetClientTeam(iBuilder);
	new iShells, iHealth, iRockets;

	switch (iLevel) {
		case 1: {
			sModel = "models/buildables/sentry1.mdl";
			iShells = 100;
			iHealth = 150;
		}
		case 2: {
			sModel = "models/buildables/sentry2.mdl";
			iShells = 120;
			iHealth = 180;
		}
		case 3: {
			sModel = "models/buildables/sentry3.mdl";
			iShells = 144;
			iHealth = 216;
			iRockets = 20;
		}
	}

	new iSentry = CreateEntityByName("obj_sentrygun");
	
	if (IsValidEdict(iSentry)) {
		// TODO: Clean this up, are all these attributes needed?
		TeleportEntity(iSentry, fOrigin, fAngle, NULL_VECTOR);
		DispatchSpawn(iSentry);

		SetEntityModel(iSentry,sModel);

		SetEntData(iSentry, FindSendPropOffs("CObjectSentrygun","m_flAnimTime"),                 51, 4 , true);
		SetEntData(iSentry, FindSendPropOffs("CObjectSentrygun","m_nNewSequenceParity"),         4, 4 , true);
		SetEntData(iSentry, FindSendPropOffs("CObjectSentrygun","m_nResetEventsParity"),         4, 4 , true);
		SetEntData(iSentry, FindSendPropOffs("CObjectSentrygun","m_iAmmoShells") ,                 iShells, 4, true);
		SetEntData(iSentry, FindSendPropOffs("CObjectSentrygun","m_iMaxHealth"),                 iHealth, 4, true);
		SetEntData(iSentry, FindSendPropOffs("CObjectSentrygun","m_iHealth"),                     iHealth, 4, true);
		SetEntData(iSentry, FindSendPropOffs("CObjectSentrygun","m_bBuilding"),                 0, 2, true);
		SetEntData(iSentry, FindSendPropOffs("CObjectSentrygun","m_bPlacing"),                     0, 2, true);
		SetEntData(iSentry, FindSendPropOffs("CObjectSentrygun","m_bDisabled"),                 0, 2, true);
		SetEntData(iSentry, FindSendPropOffs("CObjectSentrygun","m_iObjectType"),                 3, true);
		SetEntData(iSentry, FindSendPropOffs("CObjectSentrygun","m_iState"),                     1, true);
		SetEntData(iSentry, FindSendPropOffs("CObjectSentrygun","m_iUpgradeMetal"),             0, true);
		SetEntData(iSentry, FindSendPropOffs("CObjectSentrygun","m_bHasSapper"),                 0, 2, true);
		SetEntData(iSentry, FindSendPropOffs("CObjectSentrygun","m_nSkin"),                     (iTeam-2), 1, true);
		SetEntData(iSentry, FindSendPropOffs("CObjectSentrygun","m_bServerOverridePlacement"),     1, 1, true);
		SetEntData(iSentry, FindSendPropOffs("CObjectSentrygun","m_iUpgradeLevel"),             iLevel, 4, true);
		SetEntData(iSentry, FindSendPropOffs("CObjectSentrygun","m_iAmmoRockets"),                 iRockets, 4, true);

		SetEntDataEnt2(iSentry, FindSendPropOffs("CObjectSentrygun","m_nSequence"), 0, true);
		SetEntDataEnt2(iSentry, FindSendPropOffs("CObjectSentrygun","m_hBuilder"), iBuilder, true);

		SetEntDataFloat(iSentry, FindSendPropOffs("CObjectSentrygun","m_flCycle"),                     0.0, true);
		SetEntDataFloat(iSentry, FindSendPropOffs("CObjectSentrygun","m_flPlaybackRate"),             1.0, true);
		SetEntDataFloat(iSentry, FindSendPropOffs("CObjectSentrygun","m_flPercentageConstructed"),     1.0, true);

		SetEntDataVector(iSentry, FindSendPropOffs("CObjectSentrygun","m_vecOrigin"),             fOrigin, true);
		SetEntDataVector(iSentry, FindSendPropOffs("CObjectSentrygun","m_angRotation"),         fAngle, true);
		SetEntDataVector(iSentry, FindSendPropOffs("CObjectSentrygun","m_vecBuildMaxs"),         fBuildMaxs, true);
		SetEntDataVector(iSentry, FindSendPropOffs("CObjectSentrygun","m_flModelWidthScale"),     fMdlWidth, true);

		SetVariantInt(iTeam);
		AcceptEntityInput(iSentry, "TeamNum", -1, -1, 0);

		SetVariantInt(iTeam);
		AcceptEntityInput(iSentry, "SetTeam", -1, -1, 0); 
	}

	return iSentry;
}

/* DestroyBuilding()
**
** Destroys a building.
** ------------------------------------------------------------------------- */
stock DestroyBuilding(building)
{
	SetVariantInt(1000);
	AcceptEntityInput(building, "RemoveHealth");
}

/* TF2_GetClassSpeed()
**
** Returns a class' base speed.
** ------------------------------------------------------------------------- */
stock Float:TF2_GetClassSpeed(TFClassType:class)
{
    switch (class) {
        case TFClass_Scout:     return 400.0;
        case TFClass_Soldier:   return 240.0;
        case TFClass_DemoMan:   return 280.0;
        case TFClass_Medic:     return 320.0;
        case TFClass_Pyro:      return 300.0;
        case TFClass_Spy:       return 300.0;
        case TFClass_Engineer:  return 300.0;
        case TFClass_Sniper:    return 300.0;
        case TFClass_Heavy:     return 230.0;
    }
	
    return 0.0;
}